/* 
 * 	This file is part of VPN Monitor Project 7..

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package monitor.gui;

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.EnumMap;
import java.util.LinkedList;
import java.util.Map;

import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.BorderFactory;
import javax.swing.DefaultListModel;
import javax.swing.ImageIcon;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.ListCellRenderer;
import javax.swing.ListSelectionModel;
import javax.swing.ScrollPaneConstants;
import javax.swing.SpinnerNumberModel;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import monitor.gui.GuiControl.SettingsEnum;
import monitor.interfaces.EnumTypes.Keys;
import monitor.interfaces.EnumTypes.Types;
import monitor.utility.Utility;

@SuppressWarnings("serial")
class Start extends JPanel implements ColorObserver{
	private final JComponent[] texts;
	private final JComboBox[] network;
	private EnumMap<SettingsEnum, Boolean> used = null;
	private EnumMap<SettingsEnum, Double> limitLevels = null;
	private DefaultListModel listModel = null;
	private JTextArea info = null;
	private JButton startStop = null;
	private javax.swing.Timer timer = null;
	
	enum StartStop{
		START,STOP;
		@Override
		public String toString(){
			String name = super.toString();
			return name.substring(0, 1) + name.substring(1, name.length()).toLowerCase();
		}
	}
	
	Start(GuiControl control, EnumMap<SettingsEnum, String> settings){
		control.addColorObserver(this);
		
		used = new EnumMap<SettingsEnum, Boolean>(SettingsEnum.class);
		limitLevels = new EnumMap<SettingsEnum, Double>(SettingsEnum.class);
		
		LinkedList<JComboBox> network = new LinkedList<JComboBox>();
		LinkedList<JComponent> labels = new LinkedList<JComponent>();
		createPanel(control, labels, settings, network);
		
		this.network = new JComboBox[network.size()];
		network.toArray(this.network);
		
		texts = new JComponent[labels.size()];
		labels.toArray(texts);		
		Color fg = SettingsEnum.color(settings.get(SettingsEnum.COLOR_FOREGROUND));
		for(JComponent jc : texts)
			jc.setForeground(fg);
		
		timer = new javax.swing.Timer(1000, new ActionListener(){

			@Override
			public void actionPerformed(ActionEvent e) {
				String[] nets = Utility.getAvailableInterfaces();
				if(nets.length!=Start.this.network[0].getItemCount()){
					for(JComboBox box : Start.this.network){
						box.removeAllItems();
						for(String s : nets)
							box.addItem(s);
					}
				}
								
			}
			
		});
		timer.start();
	}
	
	void changeStartStop(StartStop option){
		startStop.setName(option.toString());
		startStop.setText(option.toString());
		for(JComboBox b : network)
			b.setEnabled(option == StartStop.START);
		
		if(option == StartStop.STOP)
			timer.stop();
		else
			timer.start();
	}
	
	String[] getNetworks(){
		String[] networks = new String[network.length];
		for(int i=0;i<network.length;i++)
			networks[i] = (String) network[i].getSelectedItem();
		return networks;
	}
	
	EnumMap<SettingsEnum, Boolean> getLimitsUsed(){
		return used;
	}
	
	EnumMap<SettingsEnum, Double> getLimitsLevel(){
		return limitLevels;
	}
	
	String getApplications(){
		StringBuilder applications = new StringBuilder();
		Object[] arr = listModel.toArray();
		for(Object s : arr)
			applications.append( (String) s+GuiControl.APPLICATION_DELIMITER );
		if(applications.length()>0)
			applications.delete(applications.lastIndexOf(GuiControl.APPLICATION_DELIMITER), applications.length()-1);
		return applications.toString();
	}
	
	@Override
	public void colorUpdated(SettingsEnum type, Color color){
		if(type==SettingsEnum.COLOR_FOREGROUND){
			for(JComponent c : texts)
				c.setForeground(color);
		} else if(type==SettingsEnum.COLOR_BACKGROUND){
			this.setBackground(color);
		}
	}
	
	private void createPanel(GuiControl control, LinkedList<JComponent> labels, EnumMap<SettingsEnum, String> settings, LinkedList<JComboBox> network){
		this.setLayout(new GridBagLayout());
		this.setBackground(SettingsEnum.color(settings.get(SettingsEnum.COLOR_BACKGROUND)));
		GridBagConstraints c = new GridBagConstraints();
		
		c.gridx = c.gridy = 0; c.gridwidth = 3;
		c.anchor = GridBagConstraints.CENTER; c.insets = new Insets(10, 0, 0, 0);		
		this.add(createNetworkSettings(labels, network), c);
				
		c.gridy++; c.gridwidth=1;
		this.add(createApplication(control, labels, settings), c);
		c.gridx++; c.insets = new Insets(0, 20, 0, 10);
		this.add(createInfoArea(labels, settings), c);
		c.gridx++;
		this.add(createLimitSettings(labels, settings), c);
		
		c.gridy++; c.gridx=0; c.gridwidth = 3;
		this.add(createButtons(control), c);
	}
	
	private JPanel createInfoArea(LinkedList<JComponent> labels, EnumMap<SettingsEnum, String> settings){
		JPanel panel = new JPanel(new BorderLayout());
		panel.setOpaque(false);
		panel.setPreferredSize(new Dimension(150,100));
		
		info = new JTextArea("Information");
		info.setEditable(false);
		info.setWrapStyleWord(true);
		info.setLineWrap(true);
		info.setOpaque(false);
		labels.add(info);
		
		JScrollPane scrollPane = new JScrollPane(info);
		scrollPane.setOpaque(false);
		scrollPane.getViewport().setOpaque(false);
		scrollPane.setBorder(BorderFactory.createLineBorder(Color.WHITE));
		
		panel.add(scrollPane, BorderLayout.CENTER);
		
		return panel;
	}
	
	private JPanel createApplication(GuiControl control, LinkedList<JComponent> labels, EnumMap<SettingsEnum, String> settings){
		// Setup the panel that will be returned.
		JPanel panel = new JPanel(new GridBagLayout());
		panel.addMouseListener(new MouseAdapter(){
			@Override
			public void mouseEntered(MouseEvent arg0) {
				info.setText("Application(s) that should be terminated, if the" +
						" connection specified in 'Interface' is lost.");				
			}
		});
		GridBagConstraints c = new GridBagConstraints();
		panel.setOpaque(false);
		c.gridx = c.gridy = 0; c.gridwidth=3;
		
		JLabel title = new JLabel("Application(s)");
		labels.add(title);
		panel.add(title, c);
		c.gridwidth=1;
		
		/*
		 * Creates a textfield where the application name are written,
		 * also adds a focuslistener that will remove the input text 5s
		 * after focus is lost.
		 */
		final JTextField field = new JTextField(10);
		field.addFocusListener(new FocusListener(){
			Thread t = null;
			@Override
			public void focusGained(FocusEvent arg0) {
				if(t!=null)
					t.interrupt();
			}

			@Override
			public void focusLost(final FocusEvent fe) {
				t = new Thread(){
					@Override
					public void run(){
						try {
							Thread.sleep(5000);
						} catch (InterruptedException e) {}
						((JTextField) fe.getSource()).setText(null);						
					}
				};
			}
			
		});
		
		// Creates and setup the two buttons.
		int imageSize = field.getPreferredSize().height;
		final ImageIcon[] icon = {new DrawMinusImage(imageSize, imageSize), new DrawPlusImage(imageSize, imageSize)};
		final String[] names = {"remove","add"};
		JButton[] buttons = new JButton[icon.length];
		for(int i=0;i<buttons.length;i++){
			buttons[i] = new JButton(icon[i]);
			buttons[i].setName(names[i]);
			buttons[i].setPreferredSize(new Dimension(imageSize+2, imageSize));
			buttons[i].setContentAreaFilled(false);
			buttons[i].setBorderPainted(false);
			buttons[i].addActionListener(control);
		}
		c.gridy++;
		panel.add(buttons[0], c);
		c.gridx++;
		panel.add(field, c);
		c.gridx++;
		panel.add(buttons[1], c);
		
		// Create the list which applications are added to.
		c.gridx--; c.gridy++;
		listModel = new DefaultListModel();
		String[] apps = settings.get(SettingsEnum.APPLICATION).split(GuiControl.APPLICATION_DELIMITER);
		for(String s : apps){
			if(s.length()==0)
				continue;
			listModel.addElement(s);
		}
		
		final JList list = new JList(listModel);
		list.setOpaque(false);
		list.addListSelectionListener(new ListSelectionListener(){

			@Override
			public void valueChanged(ListSelectionEvent lse) {
				JList list = (JList)lse.getSource();
				field.setText((String) list.getSelectedValue());
			}
			
		});
		list.setCellRenderer(new TransparentJListRenderer());
		list.setLayoutOrientation(JList.VERTICAL);
		list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		list.setFixedCellWidth(field.getPreferredSize().width);
		labels.add(list);
		
		// JScrollPane to which the list is added to.
		JScrollPane listPane = new JScrollPane(list);
		listPane.setOpaque(false);
		listPane.getViewport().setOpaque(false);
		listPane.setBorder(null);
		listPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
		listPane.setPreferredSize(new Dimension(field.getPreferredSize().width, field.getPreferredSize().height*4));
		panel.add(listPane, c);
		
		// Create Action for add and remove.
		AbstractAction addAction = new AbstractAction() {
			public void actionPerformed(ActionEvent ae) {
				String text = field.getText().trim();
				if (text.length() > 0)
					listModel.addElement(text);
				field.setText(null);
			}
		};
		AbstractAction removeAction = new AbstractAction(){

			@Override
			public void actionPerformed(ActionEvent ae) {
				DefaultListModel model = (DefaultListModel) list.getModel();
				if(model.size()>0 && list.getSelectedIndex()!=-1)
					model.remove(list.getSelectedIndex());
			}
			
		};
		
		// Add the created actions to appropriate component.
		buttons[0].addActionListener(removeAction);
		buttons[1].addActionListener(addAction);
		bindKey(list, KeyEvent.VK_DELETE, removeAction);
		bindKey(field, KeyEvent.VK_ENTER, addAction);
		
		return panel;
	}

	/**
	 * Binds a certain key to a specific component and attaches an action
	 * that should be triggered when the key is pressed.
	 * 
	 * @param component
	 * 			JComponent that the key should be binded to.
	 * @param key
	 * 			The key that should be used.
	 * @param action
	 * 			The action that should be used when the key is pressed.
	 */
	private void bindKey(JComponent component, int key, AbstractAction action) {
		ActionMap am = component.getActionMap();
		am.put("send_enter", action);
		InputMap im = component.getInputMap(JComponent.WHEN_FOCUSED);
		im.put(KeyStroke.getKeyStroke(key, 0), "send_enter");
	}
	
	/**
	 * Custom renderer for JList, that makes the background transparent. The renderer
	 * work by using the default background when an item is selected, it also change from
	 * setOpaque(false) to setOpaque(true) so that the background is shown.
	 */
	private class TransparentJListRenderer extends JLabel implements ListCellRenderer{
		
		@Override
		public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean hasFocus) {
			this.setText(value.toString());
			if(isSelected){
				this.setBackground((Color) UIManager.get("List.selectionBackground"));
				this.setOpaque(true);
			} else {
				this.setOpaque(false);
			}
			this.setForeground(list.getForeground());
			return this;
		}
		
	}
	
	private JPanel createButtons(GuiControl control){
		final String[] names = {"Start","Exit","Save","Update"};
		JPanel panel = new JPanel(new GridBagLayout());
		GridBagConstraints c = new GridBagConstraints();
		panel.setOpaque(false);
		
		c.gridx = c.gridy = 0; c.insets = new Insets(0, 0, 0, 20);
		for(String s : names){
			JButton button = new JButton(s);
			button.setName(s);
			button.addActionListener(control);
			button.setPreferredSize(new Dimension(90, button.getPreferredSize().height));
			panel.add(button, c);
			c.gridx++;
			if(s.equals(names[0]))
				startStop = button;
		}
		
		return panel;
	}
	
	private JPanel createNetworkSettings(LinkedList<JComponent> labels, LinkedList<JComboBox> network){
		String[] inter = Utility.getAvailableInterfaces();
		
		// Create panel main panel, the one that will be returned
		JPanel panel = new JPanel(new GridBagLayout());
		panel.addMouseListener(new MouseAdapter(){
			@Override
			public void mouseEntered(MouseEvent arg0) {
				info.setText("Interface is used for applications, if connection is lost will the applications" +
						" shutdown. Network is used to monitor traffic, so that if Interface is lost will there " +
						"still be traffic data.");
				info.setCaretPosition(0);
			}
		});
		GridBagConstraints c = new GridBagConstraints();
		panel.setOpaque(false);		
		
		c.gridx = c.gridy = 0; c.insets = new Insets(0, 0, 0, 20);
		for(SettingsEnum se : SettingsEnum.network()){
			c.gridy=0;
			JLabel label = new JLabel(se.toString());
			labels.add(label);
			panel.add(label, c);
			
			c.gridy=1;
			JComboBox options = new JComboBox(inter);
			network.add(options);
			panel.add(options, c);
			
			c.gridx++;
		}
		
		return panel;
	}
	
	private JPanel createLimitSettings(LinkedList<JComponent> labels, EnumMap<SettingsEnum, String> settings){
		// Create panel main panel, the one that will be returned
		JPanel panel = new JPanel(new GridBagLayout());
		GridBagConstraints c = new GridBagConstraints();
		panel.setOpaque(false);
		
		// Get the string representation of each key
		Keys[] limits = Keys.limits();
		String[] names = new String[limits.length];
		for(int i=0;i<limits.length;i++)
			names[i] = "Traffic limit - " + limits[i].toString();
		
		// Create the card panel, which card's are added to.
		final JPanel cards = new JPanel(new CardLayout());
		cards.setOpaque(false);
		
		// Create a drop down menu, as well as adding
		// a listener to change card
		JComboBox combo = new JComboBox(names);
		combo.addItemListener(new ItemListener(){

			@Override
			public void itemStateChanged(ItemEvent ie) {
				CardLayout cl = (CardLayout)(cards.getLayout());
				cl.show(cards, (String)ie.getItem());
			}
			
		});
		c.gridx = c.gridy = 0; c.anchor = GridBagConstraints.NORTH;
		panel.add(combo, c);
				
		// Create a card for each key where limits can be set.
		for(final Keys key : limits){
			JPanel card = new JPanel(new GridBagLayout());
			card.setOpaque(false);
			
			// Add components backwards to be able to create the listener
			c.anchor = GridBagConstraints.WEST;
			c.gridy = 0; c.gridwidth = 1;
			for(final Types type : Types.values()){
				// Fetch the correct enum for the spinner and box.
				final SettingsEnum limitEnum = SettingsEnum.valueOf(("LIMIT_SPINNER_"+key.toString()+"_"+type.toString()).toUpperCase());
				final SettingsEnum boxEnum = SettingsEnum.valueOf(("LIMIT_BOX_"+key.toString()+"_"+type.toString()).toUpperCase());
				boolean active = Boolean.parseBoolean(settings.get(boxEnum));
				double value = Double.parseDouble(settings.get(limitEnum));
				used.put(boxEnum, Boolean.parseBoolean(settings.get(boxEnum)));

				c.gridx = 2;
				JLabel suffix = new JLabel("MB");
				labels.add(suffix);
				card.add(suffix, c);
				
				c.gridx--;
				final SpinnerNumberModel model = new SpinnerNumberModel(value, 1.0, 1e6, 100.0);
				
				final JSpinner spinner = new JSpinner(model);
				if(active)
					spinner.addMouseWheelListener(createWheelListener(spinner));
				limitLevels.put(limitEnum, value);
				model.addChangeListener(new ChangeListener(){
					@Override
					public void stateChanged(ChangeEvent ce) {
						limitLevels.put(limitEnum, new Double(((SpinnerNumberModel) ce.getSource()).getNumber().doubleValue()));
					}
				});
				Dimension dimension = spinner.getPreferredSize();
				dimension.width-=35;
				spinner.setPreferredSize(dimension);
				spinner.setEnabled(active);
				card.add(spinner, c);
				
				c.gridx--;
				JCheckBox box = new JCheckBox(type.toString()+" ");
				box.setSelected(active);
				box.setOpaque(false);
				box.addItemListener(new ItemListener(){
					private MouseWheelListener mwl = null;
					
					@Override
					public void itemStateChanged(ItemEvent ie) {
						if(((JCheckBox) ie.getSource()).isSelected()){
							if(mwl == null){
								if(spinner.getMouseWheelListeners().length == 0){
									mwl = createWheelListener(spinner);
									spinner.addMouseWheelListener(mwl);									
								}
								else
									mwl = spinner.getMouseWheelListeners()[0];
							} else
								spinner.addMouseWheelListener(mwl);
							spinner.setEnabled(true);
							used.put(boxEnum, true);
							limitLevels.put(limitEnum, model.getNumber().doubleValue());
						} else{
							if(spinner.getMouseWheelListeners().length != 0)
								mwl = spinner.getMouseWheelListeners()[0];
							spinner.removeMouseWheelListener(mwl);
							spinner.setEnabled(false);
							used.put(boxEnum, false);
						}
					}
					
				});
				labels.add(box);
				card.add(box, c);
				c.gridy++;
			}
			cards.add(card, "Traffic limit - " + key.toString());
		}
		c.gridx=0; c.gridy=1;
		panel.addMouseListener(new MouseAdapter(){
			@Override
			public void mouseEntered(MouseEvent arg0) {
				info.setText("Traffic Limits, currently active:\n");
				info.setCaretPosition(0);
				for(Map.Entry<SettingsEnum, Boolean> e : used.entrySet())
					if(e.getValue())
						info.append(e.getKey().toString()+"\n");
			}
		});
		panel.add(cards, c);
		
		return panel;
	}

	/**
	 * Creates a listener to the mouse wheel, so that when a user scroll
	 * while over a JSpinner, will the values for the spinner change.
	 * @param spinner
	 * 			JSpinner to which the listener should be added to.
	 * @return
	 * 			JSpinner with mouseWheelListener added.
	 */
 	private MouseWheelListener createWheelListener(final JSpinner spinner){
		MouseWheelListener listener = new MouseWheelListener(){
			
			@Override
			public void mouseWheelMoved(MouseWheelEvent mwe) {
				if (mwe.getScrollType() != MouseWheelEvent.WHEEL_UNIT_SCROLL)
					return;
				double value = (Double)spinner.getValue(), tmp;
				value -= mwe.getUnitsToScroll();
				
				/*
				 * Change the value of the JSpinner but only if the maximum or minimum
				 * haven't been reached.  
				 */
				if(value < (tmp = (Double) ((SpinnerNumberModel) spinner.getModel()).getMinimum()) )
					value = tmp;
				else if(value > (tmp = (Double) ((SpinnerNumberModel) spinner.getModel()).getMaximum()) )
					value = tmp;
				
				spinner.setValue(value);
			}
			
		};
		return listener;
	}
 	
 	private class DrawPlusImage extends ImageIcon{		
 		private DrawPlusImage(int width, int height){
 			BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
 			Graphics2D g2 = (Graphics2D) image.getGraphics();
 			g2.setColor(Color.GREEN);
 			Rectangle2D.Float horizontalBox = new Rectangle2D.Float((1f/4f)*width, 0, (1f/2f)*width, height);
 			Rectangle2D.Float verticalBox = new Rectangle2D.Float(0, (1f/4f)*height, width, (1f/2f)*height);
 			g2.fill(horizontalBox);
 			g2.fill(verticalBox);
 			this.setImage(image);
 		} 		
 	}
 	
 	private class DrawMinusImage extends ImageIcon{
 		private DrawMinusImage(int width, int height){
 			BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
 			Graphics2D g2 = (Graphics2D) image.getGraphics();
 			g2.setColor(Color.RED);
 			Rectangle2D.Float verticalBox = new Rectangle2D.Float(0, (1f/4f)*height, width, (1f/2f)*height);
 			g2.fill(verticalBox);
 			this.setImage(image);
 		}
 	}
}
